#include <iostream>
#include <string>
#include <cstring>
#include "quickjs.h"
#include "quickjs-libc.h"
#include "../sharedcodes/wdirtools.h"
#include <sstream>
#include <fstream>
#include "../sharedcodes/wstringutils.h"
#include "wjsruntime.h"
#include "wdatetime.h"

// #include "spdlog/spdlog.h"
// #include "spdlog/sinks/stdout_color_sinks.h"
// #include "spdlog/sinks/basic_file_sink.h"
#include "../sharedcodes/wdatetime.h"

using namespace std;


string g_workingdir = "" ;// this is workingdir for one run, support read and write. this is required.
string g_modeldir = "" ;// a readonly dir for model luts and some static files. this is optional.
string g_logfilename = "./js_taskflow_worker_simple.log" ;

void initlogfile(string logfilename){
    g_logfilename=logfilename ;
    string dt = wDatetime::currentDateTimeStr() ;
    cout<<dt<<" info: init log file."<<endl; 
    ofstream ofs(g_logfilename.c_str()) ;
    if( ofs.is_open() ){
        ofs<<dt<<" info: init log file."<<endl;
        ofs.close() ;
    }
}
void loginfo(string text) {
    string dt = wDatetime::currentDateTimeStr() ;
    cout<<dt<<" info: "<<text<<endl;
    ofstream ofs(g_logfilename.c_str(), std::ios::app) ;
    if( ofs.is_open() ){
        ofs<<dt<<" info: "<<text<<endl;
        ofs.close() ;
    }else{
        cout<<"loginfo failed to open logfile."<<endl;
    }
}
void logerror(string text) {
    string dt = wDatetime::currentDateTimeStr() ;
    cout<<dt<<" error: "<<text<<endl;
    ofstream ofs(g_logfilename.c_str(), std::ios::app) ;
    if( ofs.is_open() ){
        ofs<<dt<<" error: "<<text<<endl;
        ofs.close() ;
    }else{
        cout<<"logerror failed to open logfile."<<endl;
    }
}
void logwarn(string text ) {
    string dt = wDatetime::currentDateTimeStr() ;
    cout<<dt<<" warn: "<<text<<endl;
    ofstream ofs(g_logfilename.c_str(), std::ios::app) ;
    if( ofs.is_open() ){
        ofs<<dt<<" warn: "<<text<<endl;
        ofs.close() ;
    }else{
        cout<<"logwarn failed to open logfile."<<endl;
    }
}

// !register
//read file all contents into one line.
//if anything wrong return null.
JSValue jsf_readfile_into_oneline(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    if( argc == 1 ) {
        string filename ( JS_ToCString(ctx, argv[0]) ) ;
        string text1line ;
        bool isok = wDirTools::readFullTextIntoOneLine( filename ,text1line ) ;
        if( isok==false ) {
            logerror( string("wDirTools::readFullTextIntoOneLine failed to read ")+filename ) ;
            return JS_NULL ;
        }else {
            return JS_NewString( ctx, text1line.c_str() );
        }
    }else {
        return JS_NULL ;
    }
}

// !register
//read file all contents with newline.
//if anything wrong return null.
JSValue jsf_readfile_into_lines(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    if( argc == 1 ) {
        string filename ( JS_ToCString(ctx, argv[0]) ) ;
        string textlines ;
        bool isok = wDirTools::readFullTextIntoLines( filename ,textlines ) ;
        if( isok==false ) {
            logerror( string("wDirTools::readFullTextIntoLines failed to read {}.")+ filename) ;
            return JS_NULL ;
        }else {
            return JS_NewString( ctx, textlines.c_str() );
        }
    }else {
        return JS_NULL ;
    }
}


 
// ! register
//write string into text file
//js writetext_to_file( text , filename )
//good return true, else return false.
JSValue jsf_writetext_to_file( JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv ) {
    if( argc == 2 ) {
        string text1(     JS_ToCString(ctx, argv[0])  ) ;
        string filename ( JS_ToCString(ctx, argv[1])  ) ;
        ofstream ofs( filename.c_str() ) ;
        if( ofs.is_open() ) {
            ofs<<text1 ;
            ofs.close() ;
            return JS_TRUE ;
        }else{
            return JS_FALSE;
        }
    }else {
        return JS_FALSE ;
    }
}



//call bash command
int bash_operator(const string& taskid, const string& cmd) {
    loginfo( string( "bash_operator taskid {}, try to run:" ) + taskid ) ;
    string logfile = g_workingdir + "/" + taskid + ".log" ;
    string cmd2 = cmd + ">" + logfile ;
    loginfo(cmd2) ;
    int ret = system( cmd2.c_str() ) ;
    loginfo( string("return value:{}")+ wStringUtils::int2str(ret) ) ;
    return ret ;
}

//call bash command
//if cmd ok return command retval of integer, else return -1.
JSValue jsf_bash_operator(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    if( argc==2 ) {
        const char* taskid0 = JS_ToCString(ctx, argv[0]);
        const char* cmd0 = JS_ToCString(ctx, argv[1]);
        string taskid (  taskid0) ;
        string cmd( cmd0 ) ;
        JS_FreeCString(ctx,taskid0);
        JS_FreeCString(ctx,cmd0);
        int reti = bash_operator(taskid,cmd) ;
        JSValue res = JS_NewInt32(ctx,reti) ;
        return res ;
    }else{
        logerror("bash_operator failed to call, argc not 2.") ;
        JSValue res = JS_NewInt32(ctx, -1) ;
        return res ;
    }
}


//log, multi params, always return true.
JSValue jsf_log(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    string logtext("js: ");
    for(int i = 0 ; i<argc ; ++ i )
    {
        const char* tstr =  JS_ToCString(ctx, argv[i]);
        if( tstr !=nullptr ) {
            string str1 ( tstr ) ;
            logtext += str1 + " " ;
            JS_FreeCString(ctx, tstr);
        }else{
            //spdlog::warn("jsf_log JS_ToCString get null char* ");
            logtext += " exceptions of jsf_log JS_ToCString get null char* " ;
        }
    }
    loginfo(logtext) ;
    return JS_TRUE ;
}



void printShowHelp() {
    cout<<"--- start of help info ---"<<endl;
    cout<<"build-in functions:"<<endl ;
    cout<<"var jsonObj = json_from_file(filename);"<<endl;
    cout<<"var retval=bash_operator('taskid','commandstring') ; "<<endl;
    cout<<"log('text') ; "<<endl;
    cout<<"var onelinetext=readfile_into_oneline(filename) ; "<<endl;
    cout<<"var lines =readfile_into_oneline(filename) ; "<<endl;
    cout<<"var res=writetext_to_file(text,filename) ; "<<endl;
    cout<<"--- end of help info ---"<<endl;
}


int main(int argc,char* argv[])
{
    cout << "A simple task flow worker and executor based on Quickjs. 2024-1-4" << endl;

    string logfilename = string(argv[0])+ ".log" ;
    initlogfile(logfilename) ;

    string version = "v1.0.0.1 2024-1-4" ;//add shell json
    cout<<version<<endl ;
    
    loginfo( string("version: ") + version) ;

    cout<<"usage:js_taskflow_worker_simple script.js input.json inputcontent "<<endl ;
    cout<<" script.js: the script filename."<<endl ;
    cout<<" input.json: input json filename, can be null."<<endl ;
    cout<<" inputcontent: input plain text, can be null."<<endl ;
    cout<<"---------"<<endl;
    cout<<"buildin global varables:"<<endl;
    cout<<"var dag={workingdir};// workingdir is ./ "<<endl ;
    cout<<"var inputjson=jsoncontent_of_inputjson; // if null use {} "<<endl ;
    cout<<"var inputtext=inputcontent; // if null use '' "<<endl ;
    cout<<"---------"<<endl;
    printShowHelp() ;

    if( argc !=4 ) {
        logerror("argc not 4") ;
        return 11 ;
    }

    string workingdir="./" ;
    string scriptfile=argv[1] ;
    string inputjsonfilename=argv[2];
    string inputplaintext=argv[3];

    loginfo( string("workingndir is ") + workingdir ) ;

    int64_t start_ms = wDatetime::currentMilliSeconds() ;

    cout<<"workingdir "<<workingdir<<endl ;
    cout<<"scriptfile "<<scriptfile<<endl ;
    cout<<"inputjsonfilename "<<inputjsonfilename<<endl ;
    cout<<"inputplaintext "<<inputplaintext<<endl ;

    loginfo( scriptfile ) ;
    loginfo( inputjsonfilename ) ;
    loginfo( inputplaintext ) ;

    string error ;

    g_workingdir = workingdir ;//also workingdir
    g_modeldir = "./" ;//this is optional
 
    string scriptText ;
    bool ok1 = wDirTools::readFullTextIntoLines(scriptfile,scriptText) ;
    if( ok1==false ) {
        logerror( string("failed to open script file {}")+ scriptfile) ;
        return 12 ;
    }
    string inputjsonText ;
    if( inputjsonfilename!="null" ) {
        bool ok2 = wDirTools::readFullTextIntoLines(inputjsonfilename,inputjsonText) ;
        if( ok2==false ) {
            logerror( string("failed to open input file {}")+ inputjsonfilename) ;
            return 13 ;
        }
    }else{
        inputjsonText = "{}" ;
    }
    loginfo( string("inputjsonText:") + inputjsonText ) ;

    stringstream ss ;

    //js json_from_file(filename)
    ss<<"function json_from_file(filename){ var text1=readfile_into_oneline(filename) ;  return JSON.parse(text1); }\n\n\n" ;

    ss<<"var dag={"
        << "  \"workingdir\":\"" << g_workingdir  << "\" "
        << ", \"modeldir\":\""   << g_modeldir    <<"\" "
        << "}; \n" ;
    ss<<"var context=dag;\n" ;
    ss<<"var inputjson="<< inputjsonText <<";\n" ;
    ss<<"var inputtext='"<< inputplaintext <<"';\n" ;
    ss<<scriptText ;

    string fullscript = ss.str() ;
    loginfo("write full script" ) ;
    string fullscriptfilename = g_workingdir + "/full.js" ;
    ofstream ofs(fullscriptfilename.c_str()) ;
    if( ofs.is_open() ) {
        ofs<<fullscript ;
        ofs.close() ;
    }else {
        logerror( string("failed to write full script {}" )+ fullscriptfilename) ;
        return 14 ;
    }

    loginfo("WJsRuntime init" ) ;
    WJsRuntime wjs ;
    bool jsok = wjs.init( 0, error) ;//0 for default
    if( jsok==false ) {
        logerror( string("failed to init WJsRuntime {}" )+ error) ;
        return 21 ;
    }

    JSValue global_obj;
    global_obj = JS_GetGlobalObject(wjs.getContext());
    //js  bash_operator(taskid, command)
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "bash_operator",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_bash_operator, "bash_operator", 2)
                      );

  

    //js log(...)
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "log",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_log, "log", 1)
                      );
    //js readfile_into_oneline( filename )
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "readfile_into_oneline",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_readfile_into_oneline, "readfile_into_oneline", 1)
                      );
    //js readfile_into_lines( filename )
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "readfile_into_lines",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_readfile_into_lines, "readfile_into_lines", 1)
                      );
    
    
    //js writetext_to_file(text,filename)
    JS_SetPropertyStr(wjs.getContext(),
                      global_obj, "writetext_to_file",
                      JS_NewCFunction(wjs.getContext(),
                                    jsf_writetext_to_file, "writetext_to_file", 2)
                      );
     
     


    loginfo("running full script..." ) ;
    JSValue runfullscriptResult = JS_Eval( wjs.getContext() , fullscript.c_str() , fullscript.length() , "<eval>",  0) ;
    if( JS_VALUE_GET_TAG(runfullscriptResult)== JS_TAG_EXCEPTION     ) {
        JSValue exceptionvalue = JS_GetException( wjs.getContext() );
        const char * exceptionCharbuff = JS_ToCString( wjs.getContext() , exceptionvalue ) ;
        loginfo( string("exception : {} " )+ exceptionCharbuff ) ;
        JS_FreeValue(wjs.getContext() , exceptionvalue);
    }


    JS_FreeValue( wjs.getContext() , global_obj ) ;
    JS_FreeValue( wjs.getContext() , runfullscriptResult ) ;

    {
        int64_t end_ms = wDatetime::currentMilliSeconds() ;
        int fullseconds = (end_ms - start_ms) / 1000 ;
        int tminutes = fullseconds / 60 ;
        int tsecs = fullseconds%60 ;
        loginfo( string("duration ") + wStringUtils::int2str( tminutes ) + " min " + wStringUtils::int2str(tsecs)+" sec " ) ;
    }

    loginfo("done." ) ;
    return 0;
}
